iT邦幫忙

2026 iThome 鐵人賽

DAY 9
0
Kubernetes

不是背 YAML!30 天從零打造 Kubernetes 微服務:從本機實戰一路到 CKA系列 第 9

Day 9|Service 與 DNS:Pod IP 一直變,其他服務到底怎麼找到它?

  • 分享至 

  • xImage
  •  

來到第9天啦!
昨天我們不斷更新 Deployment。

過程中:

舊 Pod 消失
新 Pod 出現

也代表:

Pod IP 可能一直改變。

所以其他 Application 絕對不能寫:

10.244.1.17

直接找某個 Pod。

這就是為什麼需要:

Service

存在的原因。


Pod 本來就不應該被當成永久存在

先:

kubectl get pods -n cka-lab -o wide

記住某個 Pod IP。

刪掉:

kubectl delete pod POD_NAME -n cka-lab

等 Deployment 補新的。

再:

kubectl get pods -n cka-lab -o wide

新 Pod IP 已經不同。

所以 Application 不應依賴 Pod IP。

https://ithelp.ithome.com.tw/upload/images/20260907/20168537PBQChXIUOX.png


建立 Service

  • kubectl expose:
    「將原本私有或內部的網路服務公開」
    ,讓群集(Cluster)內部或外部的其他應用程式能夠存取它。在 Kubernetes (K8s) 中,這條指令會自動幫你建立一個 Service 元件。它會根據你指定的 Pod、Deployment 或 ReplicaSet 的標籤(Labels),自動設定對應的選擇器(Selector),把流量正確導向你的應用程式。
kubectl expose deployment api \
  --name=api \
  --port=80 \
  --target-port=80 \
  -n cka-lab

kubectl expose 的核心功能就是把一個已經存在的資源(在這裡是名為 api 的 Deployment)公開出來,並為它建立一個全新的 Service 元件

  • 指令介紹:
    • kubectl expose deployment api:告訴 K8s「我要幫一個叫做 apiDeployment 建立 Service」。
    • --name=api:新建立的 Service 名字 叫做 api
    • --port=80:這個 Service 監聽的埠口80(群集內部拜訪此 Service 時使用的 Port)。
    • --target-port=80:流量轉發到後端 Pod 的埠口80
    • -n cka-lab:在名為 cka-lab命名空間(Namespace) 執行此操作。

查看:

kubectl get svc -n cka-lab

https://ithelp.ithome.com.tw/upload/images/20260907/20168537QX0eaRbfuW.png


porttargetPort

非常容易搞混。

假設:

Service
Port 80

送進:

Container
Port 8000

就是:

Client
↓
Service :80
↓
Pod :8000

因此:

ports:
  - port: 80 (Service 提供給別人進來的)
    targetPort: 8000 (Service 要送去的port)

port 是:

Service 對外提供的 Port。

targetPort

後端 Pod Application 真正 Listen 的 Port。

目前 nginx 剛好兩個都是 80。

後面 FastAPI 就會變成:

Service 80
→
Container 8000

Service 怎麼知道哪些 Pod 是 Backend?

查看:

kubectl get service api \
  -n cka-lab \
  -o yaml

你會看到:

selector:
  app: api

https://ithelp.ithome.com.tw/upload/images/20260907/20168537o9sqK1ixYm.png

還記得 Day 6?

Service 根本不知道:

Deployment 是誰。

它只知道:

我要標籤是 app=api 的 Pod。

因此:

Service
  │ selector app=api
  ▼
Pod A app=api
Pod B app=api
Pod C app=api

EndpointSlice

https://ithelp.ithome.com.tw/upload/images/20260907/20168537u71oTzeHtO.png
是 Kubernetes 用來取代傳統 Endpoints 資源的解決方案,專門解決大規模叢集下的網路擴展性(Scalability)瓶頸

核心問題:傳統 Endpoints 怎麼了?

在原生設計中,一個 Service 只對應一個 Endpoints 物件:

  • 當一個 Service 後面有上千個 Pod 時,只要有任何一個 Pod IP 變更(如重新啟動),整個包含數千筆 IP 的大物件就要重新序列化並同步給全叢集每個節點的 kube-proxy
  • 這會造成 etcd 負載過高、API Server 頻寬塞爆,以及節點 CPU 飆高。

EndpointSlice 的解法:切片分頁(Sharding)

Kubernetes 將 Endpoints 拆分成多個「切片(Slices)」:

  • 分塊存儲: 預設情況下,每個 EndpointSlice 最多只容納 100 個 Endpoint
  • 局部更新: 若 Service 後方有 1000 個 Pod(分成 10 個 EndpointSlices),當其中 1 個 Pod 變動時,只會觸發那 1 個 Slice(100 筆資料)的更新與傳輸,其餘 9 個不受影響。
  • 大幅降低開銷: 顯著減輕 API Server 與 etcd 負擔,讓單一 Service 可輕鬆擴展至上萬個 Pod。
    查看:
kubectl get endpointslices -n cka-lab

https://ithelp.ithome.com.tw/upload/images/20260907/20168537ek0rlc2xtt.png

此圖可看到 名為 api 的 Service 目前找到了 2 個健康的後端 Pod,且流量已經準備好可以打給它們了。你會看到 Service 對應的 Endpoint。

各欄位拆解如下:

  • NAME (api-r4w2m):
    這是由 Kubernetes 自動生成的 EndpointSlice 名稱。它以對應的 Service 名稱 api 開頭,後綴隨機字串。
  • ADDRESSTYPE (IPv4):
    當前叢集網路使用的是 IPv4 位址協定。
  • PORTS (80):
    這群後端 Pod 對外接收連線的目標連接埠(TargetPort)是 80。
  • ENDPOINTS (10.244.1.9,10.244.2.8):這是最重要的核心資訊。 代表目前有兩個實際運行的 Pod IP 位址。
    • 你可以執行 kubectl get pods -n cka-lab -o wide,會發現這兩個 IP 正好就是你那兩個 api Pod 的 IP。
    • 這表示 Pod 已經通過了 Readiness Probe(就緒檢查),Service 會透過負載平衡把流量分發到這兩個 IP。
  • AGE (17m):
    這個切片物件已經建立了 17 分鐘。

白話比喻

  • Service 像是總機櫃台(有一個固定的虛擬 IP,例如 10.96.x.x)。
  • EndpointSlice 就像總機桌上的「分機通訊錄名單」。
  • 截圖中的結果代表:總機現在名單上有兩個人可以接電話,號碼分別是 10.244.1.9:8010.244.2.8:80

如果這時候你將 Deployment 擴展到 3 個 Pod,這裡的 ENDPOINTS 就會自動變成 3 個 IP;反之若 Pod 掛掉或未就緒,對應的 IP 就會立刻從這裡被移除。

可以理解:

Service 規則
↓
EndpointSlice
↓
目前真正可送流量的 Pod IP

因此 Service 打不到時:

EndpointSlice

是超級重要的排錯線索。


接下來是 Kubernetes Service、DNS 解析與 Troubleshooting

1. 從 Cluster 內部測試 Service

在 Kubernetes 中,Service 的 ClusterIP 與內部域名只能在叢集內部的網路環境被訪問。一般我們會建立一個「一次性(Ephemeral)的測試 Pod」來驗證網路與服務連線。

推薦執行指令

kubectl run test-curl -n cka-lab --image=busybox:1.28 --restart=Never -- wget -O- http://api

https://ithelp.ithome.com.tw/upload/images/20260907/20168537UFaoB9BgRb.png

指令參數逐行詳解

  • kubectl run test-curl

  • 作用:在叢集中建立並執行一個名為 test-curl 的 Pod。

  • -n cka-lab(或 --namespace=cka-lab

  • 作用:指定將這個測試 Pod 建立在 cka-lab 命名空間下。同命名空間才能直接使用「Service 短名稱」發送請求。

  • --image=busybox:1.28

  • 作用:指定 Pod 運行的容器映像檔。

  • **為何推薦 busybox:1.28**

  • 體積極小(數 MB)、拉取極快。

  • 內建標準 shell、wgetnslookup 工具。

  • CKA 考試環境中最穩定,不會像 curlimages/curl 有預設 entrypoint 覆蓋或引數解析問題。

  • --restart=Never

  • 作用:告訴 Kubernetes 不要建立 Deployment 或 ReplicaSet,而是建立一個純粹的獨立 Pod。當裡面的指令執行完畢(不管成功或失敗)就不會自動重啟。

  • wget -O- http://api

  • 作用:在容器內執行的網路請求指令。

  • -O-(大寫 O 接減號 -):將取得的網頁內容導向標準輸出(stdout),直接印在終端畫面上,而不是存成檔案。

  • http://api:目標 Service 的名稱與 Port 80(HTTP 預設)。

驗證: 查看該 Pod 的日誌輸出:

kubectl logs test-curl -n cka-lab

執行成功之現象

若連線成功,終端機會直接印出 Nginx 的 HTML 首頁原始碼:

https://ithelp.ithome.com.tw/upload/images/20260907/20168537yFpWGN8lpj.png


2. 為什麼只寫 http://api 就會通?(CoreDNS 底層原理)

我們在指令中完全沒有輸入任何 IP 位址(如 10.96.x.x 或 Pod IP),但它依然能找到目標服務。背後是靠 Kubernetes 的內建 DNS(CoreDNS)與 Linux 的解析機制協同完成。

Service 的完整網域名稱(FQDN)

https://ithelp.ithome.com.tw/upload/images/20260907/20168537iGnFDGMJmX.jpg

Kubernetes Service 的標準完整格式為:

<SERVICE_NAME>.<NAMESPACE>.svc.cluster.local

因此,在 cka-lab 命名空間下的 api Service,其完整 FQDN 為:

api.cka-lab.svc.cluster.local

  • api:Service 的名稱。
  • cka-lab:所屬的 Namespace。
  • svc:代表這是一條 Service 類型的 DNS 紀錄(區別於 Pod IP 紀錄)。
  • cluster.local:叢集的預設網域後綴(Domain Suffix)。

容器內的自動補全機制(/etc/resolv.conf

當 Pod 建立時,Kubelet 會自動在容器內的 /etc/resolv.conf 寫入 DNS 設定:

nameserver 10.96.0.10
search cka-lab.svc.cluster.local svc.cluster.local cluster.local
options ndots:5

  1. nameserver 10.96.0.10:指向叢集內部 CoreDNS 的 ClusterIP。
  2. search ...:搜尋後綴列表。當你在 Pod 裡發出 api 請求時,系統會依序拿後綴拼接:
  • 第一次嘗試拼接:api + cka-lab.svc.cluster.local ➔ 成功命中 CoreDNS 紀錄!
  1. 微服務應用實務
    在同一個命名空間部署的微服務,設定檔或環境變數不需要 hardcode IP,直接使用 Service 名稱即可:
REDIS_HOST=redis
DATABASE_HOST=postgres

  1. 跨 Namespace 存取規則
    若呼叫端在 default 命名空間,想呼叫 cka-lab 的服務,不能只寫 api(會被補成 api.default... 導致解析失敗),必須至少明確帶上 Namespace:
http://api.cka-lab
# 或使用完整 FQDN
http://api.cka-lab.svc.cluster.local


3. 故意弄壞 Service(模擬常見故障)

在 Kubernetes 中,最常見的架構失誤之一是 「Service 的 Selector 與 Pod 的 Label 對不上」

步驟 A:竄改 Service 的 Selector

執行 patch 指令,把 Service 監聽的標籤改為不存在的 app: wrong

kubectl patch service api \
  -n cka-lab \
  -p '{"spec":{"selector":{"app":"wrong"}}}'

https://ithelp.ithome.com.tw/upload/images/20260907/20168537romnFCmSKv.png

  • 參數解析
  • patch service api:對名為 api 的 Service 進行局部設定修改(不需要重寫整個 YAML)。
  • -p--patch):傳入要覆蓋的 JSON / YAML 格式字串。
  • '{"spec":{"selector":{"app":"wrong"}}}':將 spec.selector 欄位改為只尋找帶有標籤 app=wrong 的 Pod。

步驟 B:觀察 Pod 與 Endpoints 的狀態反差

檢查 Pod 狀態:

kubectl get pods -n cka-lab

https://ithelp.ithome.com.tw/upload/images/20260907/20168537VaixVCpxAR.png

  • 現象:Pod 狀態依然是綠燈的 Running

檢查 Service 的後端轉發清單(Endpoints / EndpointSlices):

kubectl get endpoints api -n cka-lab
# 或新版建議:
kubectl get endpointslices -n cka-lab

  • 現象
    ENDPOINTS 欄位會變成 <none>,原本掛在後面的 Pod IP(如 10.244.1.9:80)全部消失。

https://ithelp.ithome.com.tw/upload/images/20260907/20168537jhkt39TscO.png


步驟 C:再次測試連線

kubectl exec -n cka-lab nginx -- curl -sI http://api

https://ithelp.ithome.com.tw/upload/images/20260907/20168537zFyfHIZJGT.png

curlExit Code 7 在官方定義中是:

CURLE_COULDNT_CONNECT (7): Failed to connect() to host or proxy.
(即底層作業系統回傳了 Connection refused

為什麼會噴 Exit Code 7?

  1. DNS 解析成功nginx Pod 成功將 api 解析為 Service 的 ClusterIP(10.96.204.166)。

  2. 連線被拒絕:封包送到該 IP 後,因為你之前執行了:Bash

    kubectl patch service api -n cka-lab -p '{"spec":{"selector":{"app":"wrong"}}}'
    

    Service 的後端轉發清單(Endpoints)是空的,核心網路找不到任何實體 Pod 來接收流量,直接回絕 TCP 連線,因此 curl 回報 Exit Code 7。


4. 修復 Service 並驗證

將 Service 的 Selector 改回正確匹配 Nginx Pod 的標籤(app: api):

kubectl patch service api \
  -n cka-lab \
  -p '{"spec":{"selector":{"app":"api"}}}'

https://ithelp.ithome.com.tw/upload/images/20260907/201685375I7YNF7R3r.png

驗證修復結果

  1. 確認 Endpoints 重新恢復
kubectl get endpoints api -n cka-lab

https://ithelp.ithome.com.tw/upload/images/20260907/20168537vpTOrv2Du0.png

  • 輸出ENDPOINTS 欄位應重新出現 Pod 的 IP 與 Port(例如 10.244.1.9:80,10.244.2.8:80)。
  1. 重新發送測試請求
kubectl exec -n cka-lab nginx -- curl -sI http://api

https://ithelp.ithome.com.tw/upload/images/20260907/20168537kCpkUUxSdQ.png

  • kubectl exec:在 Kubernetes 叢集中「已經正在運行的 Pod」內部執行指令(類似遠端連進容器操作)。

  • -n cka-lab:指定目標 Pod 所在的命名空間(Namespace)為 cka-lab

  • nginx:指定要在哪一個 Pod 裡面執行(借用叢集裡原本就活著、名為 nginx 的 Pod)。

  • --(參數分隔符號)

  • -- 前面是給 kubectl 自己的參數。

  • -- 後面則是直接丟進 nginx 容器內部執行的指令。

  • curl -sI http://api:在容器內執行的網路請求指令:

  • -s(silent):安靜模式,不印出下載進度條與統計資訊。

  • -I(head only)只抓取 HTTP 標頭(Headers),不下載整份 HTML 網頁內容,適合用來快速確認服務狀態碼與伺服器資訊。

  • http://api:目標服務名稱。同命名空間下會由 CoreDNS 自動解析成 Service 的 ClusterIP。

輸出結果解析

HTTP/1.1 200 OK
Server: nginx/1.27.5
Date: Mon, 07 Sep 2026 11:14:34 GMT
Content-Type: text/html
Content-Length: 615
Last-Modified: Wed, 16 Apr 2025 12:55:34 GMT
Connection: keep-alive
ETag: "67ffa8c6-267"
Accept-Ranges: bytes

  • HTTP/1.1 200 OK:最關鍵的一行。狀態碼 200 代表請求成功送達、後端正常處理並成功回應。
  • Server: nginx/1.27.5:代表處理這個請求的後端伺服器是版本為 1.27.5 的 Nginx。
  • Content-Type: text/htmlContent-Length: 615:代表如果下載網頁主體,會拿到一份長度為 615 Bytes 的 HTML 內容(即標準的 "Welcome to nginx!" 預設頁面)。

結果代表的 Kubernetes 意義

這份回應證明了四件事:

  1. CoreDNS 解析暢通nginx Pod 順利將短域名 api 解析為 Service 的 ClusterIP。
  2. Service 修復成功:先前被竄改為 app: wrong 的 Selector 已經修正回 app: api
  3. Endpoints 正常轉發:Service 背後成功掛上了可用的 Pod IP(不再是 <none>),kube-proxy / CNI 正確將流量轉發給後端容器。

Day 9 小結

今天要建立這張圖:

Client
↓
Service
↓
Selector
↓
EndpointSlice
↓
Pod

Service 的價值不是:

讓 Pod 可以跑。

而是:

替一群不穩定、隨時可能被替換的 Pod 提供穩定入口。

明天開始,不再使用 nginx 當主角。

我們要建立真正屬於這系列的 Application!


上一篇
Day 8|Rolling Update 與 Rollback:Kubernetes 怎麼做到不中斷更新?
下一篇
Day 10|不再部署 nginx:完整實戰 自己寫 FastAPI、Build Image,再丟進 k8s
系列文
不是背 YAML!30 天從零打造 Kubernetes 微服務:從本機實戰一路到 CKA10
圖片
  熱門推薦
圖片
{{ item.channelVendor }} | {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言